package dnl.dev.memlog;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.GridLayout;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Set;

import javax.swing.BorderFactory;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;

import org.apache.commons.math.stat.DescriptiveStatistics;
import org.jdesktop.swingx.JXTable;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.CategoryLabelPositions;
import org.jfree.chart.labels.ItemLabelAnchor;
import org.jfree.chart.labels.ItemLabelPosition;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.xy.IntervalXYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.TextAnchor;

import dnl.dev.memlog.parser.ClassInstanceCount;
import dnl.util.ui.Colors;
import dnl.util.ui.ProgressDialog;
import dnl.util.ui.ProgressListener;

public class HistogramStatisticsView extends View {

	// private JPanel centerJP = new JPanel(new BorderLayout());
	private JPanel statsJP = new JPanel(new BorderLayout());
	private JPanel chartJP = new JPanel(new BorderLayout());
	private JFreeChart freeChart;
	private JXTable classesTable = new JXTable();
	private JLabel classNameJL = new JLabel();
	private JTextArea statsTA = new JTextArea();

	private MemlogContext memlogContext;

	private static DateFormat dateFormat = new SimpleDateFormat();

	public HistogramStatisticsView() {
		super("Statistics");
		this.setLayout(new BorderLayout());
		initPanels();
	}

	@Override
	public void present(final MemlogContext memlogContext) {
		this.memlogContext = memlogContext;

		final Window windowAncestor = SwingUtilities.getWindowAncestor(this);

		final ProgressListener progressListener = new ProgressListener() {
			@Override
			public void finished() {
			}

			@Override
			public void progressed(final int precentage) {
				ProgressDialog.showProgress(windowAncestor, "Processing histograms dir",
						precentage);
			}
		};
		if (!memlogContext.isDataPrepared()) {
			ProgressDialog.setProgressText(windowAncestor, "Populating UI");
			memlogContext.prepareData(progressListener);
			ProgressDialog.finished(windowAncestor);
		}
		populateClassesTable(null);
	}

	private void initPanels() {
		showChart(null);
		JPanel classNamesPanel = getClassNamesPanel();
		initChartsJP(classNamesPanel);

		JSplitPane splitPane = new JSplitPane(SwingConstants.VERTICAL, classNamesPanel, statsJP);
		this.add(BorderLayout.CENTER, splitPane);
		splitPane.setDividerLocation(200);
	}

	private void initChartsJP(JPanel classNamesPanel) {
		classNamesPanel.setFont(classNamesPanel.getFont().deriveFont(Font.BOLD));
		this.statsJP.add(BorderLayout.SOUTH, classNameJL);
		JSplitPane splitPane = new JSplitPane(SwingConstants.HORIZONTAL, chartJP, new JScrollPane(
				statsTA));
		splitPane.setDividerLocation(400);
		this.statsJP.add(BorderLayout.CENTER, splitPane);
	}

	private JPanel getClassNamesPanel() {
		JPanel jp = new JPanel(new BorderLayout());
		classesTable.setModel(new DefaultTableModel(new String[] { "" }, 10));
		classesTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
			@Override
			public void valueChanged(ListSelectionEvent e) {
				if (e.getValueIsAdjusting())
					return;
				int modelIndex = classesTable.convertRowIndexToModel(classesTable.getSelectedRow());
				if(modelIndex < 0){
					return;
				}
				String selectedClass = (String) classesTable.getModel().getValueAt(modelIndex, 0);
				showChart(selectedClass);
			}
		});
		jp.add(BorderLayout.CENTER, new JScrollPane(classesTable));
		jp.setBorder(BorderFactory.createTitledBorder("Classes"));

		JPanel jp2 = new JPanel(new GridLayout(2, 1));
		final JCheckBox fqnCheckBox = new JCheckBox("Fully qualified names.", true);
		fqnCheckBox.addItemListener(new ItemListener() {
			@Override
			public void itemStateChanged(ItemEvent e) {
				if (e.getStateChange() == ItemEvent.SELECTED) {
					JOptionPane
							.showMessageDialog(SwingUtilities
									.getWindowAncestor(HistogramStatisticsView.this),
									"Not implemented yet");
				}
			}
		});
		final JTextField filterTF = new JTextField(2);
		filterTF.setBorder(BorderFactory.createTitledBorder("Filter"));
		filterTF.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				populateClassesTable(filterTF.getText());
			}
		});
		jp2.add(fqnCheckBox);
		jp2.add(filterTF);
		jp.add(BorderLayout.SOUTH, jp2);

		return jp;
	}

	private void populateClassesTable(String filter) {
		Set<String> classNames = memlogContext.getClassNames();
		List<String> cn = new ArrayList<String>();
		for (String className : classNames) {
			if (filter == null || className.startsWith(filter)) {
				cn.add(className);
			}
		}

		List<StatisticalData> statisticalData = calcVariances(cn);
		TableModel tableModel = new TModel(cn, statisticalData);
		classesTable.setModel(tableModel);
	}

	public void showChart(String className) {
		if (memlogContext == null) {
			return;
		}
		if (SamplingType.PERIODIC.equals(memlogContext.getMemlogProject().getSamplingType())) {
			createXyBarChart(className);
		} else {
			createSimpleBarChart(className);
		}
		ChartPanel chartPanel = new ChartPanel(freeChart);
		classNameJL.setText("Showing mem histo for class: " + className);
		this.chartJP.removeAll();
		this.chartJP.add(BorderLayout.CENTER, chartPanel);
		this.chartJP.doLayout();

		if (className != null) {
			List<ClassInstanceCount> stats = memlogContext.getStats(className);
			Collections.sort(stats, new Comparator<ClassInstanceCount>() {
				@Override
				public int compare(ClassInstanceCount o1, ClassInstanceCount o2) {
					return o1.getSamplingDate().compareTo(o2.getSamplingDate());
				}
			});
			StringBuilder sb = new StringBuilder();
			for (ClassInstanceCount cic : stats) {
				if (cic.getSampleLabel() != null) {
					sb.append(cic.getSampleLabel());
				} else {
					sb.append(dateFormat.format(cic.getSamplingDate()));
				}
				sb.append("=");
				sb.append(cic.getNumberOfInstances());
				if (cic.getSampleLabel() != null) {
					sb.append("   (");
					sb.append(dateFormat.format(cic.getSamplingDate()));
					sb.append(")");
				}
				sb.append("\n");
			}
			statsTA.setText(sb.toString());
		}
	}

	private void createXyBarChart(String className) {
		IntervalXYDataset dataset = null;
		if (className != null) {
			dataset = createXyDataset(className);
		}
		freeChart = ChartFactory.createXYBarChart("Object Counts", "time (sec)", false,
				"number of instances", dataset, PlotOrientation.VERTICAL, false, false, false);
	}

	private void createSimpleBarChart(String className) {
		DefaultCategoryDataset categoryDataset = new DefaultCategoryDataset();
		List<ClassInstanceCount> histoStats = memlogContext.getStats(className);
		Collections.sort(histoStats, new Comparator<ClassInstanceCount>() {
			@Override
			public int compare(ClassInstanceCount o1, ClassInstanceCount o2) {
				return o1.getSamplingDate().compareTo(o2.getSamplingDate());
			}
		});

		for (ClassInstanceCount cic : histoStats) {
			categoryDataset.addValue(cic.getNumberOfInstances(), "", cic.getSampleLabel());
		}

		freeChart = ChartFactory.createBarChart3D("Object Counts", // Title
				"Sample Label", // X-Axis label
				"number of instances", // Y-Axis label
				categoryDataset, // Dataset
				PlotOrientation.VERTICAL, false, true, false);
		CategoryPlot plot = freeChart.getCategoryPlot();

		BarRenderer renderer = (BarRenderer) plot.getRenderer();
		renderer.setMaximumBarWidth(.01);

		// set up gradient paints for series...
		final GradientPaint gp0 = new GradientPaint(0.0f, 0.0f, Colors.darkGreen, 0.0f, 0.0f,
				Color.lightGray);

		renderer.setSeriesPaint(0, gp0);

		final ItemLabelPosition p2 = new ItemLabelPosition(ItemLabelAnchor.OUTSIDE12,
				TextAnchor.CENTER_LEFT, TextAnchor.CENTER_LEFT, -Math.PI / 2.0);
		renderer.setPositiveItemLabelPositionFallback(p2);
		final CategoryAxis domainAxis = plot.getDomainAxis();
		domainAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_45);

	}

	private List<StatisticalData> calcVariances(List<String> classNames) {
		List<StatisticalData> variances = new ArrayList<StatisticalData>();
		for (String className : classNames) {
			StatisticalData statisticalData = calculateStatisticalData(className);
			variances.add(statisticalData);
		}
		return variances;
	}

	private StatisticalData calculateStatisticalData(String className) {
		DescriptiveStatistics stats = DescriptiveStatistics.newInstance();
		List<ClassInstanceCount> histoStats = memlogContext.getStats(className);

		for (ClassInstanceCount dateAndNumber : histoStats) {
			stats.addValue(dateAndNumber.getNumberOfInstances());
		}
		double variance = stats.getVariance();
		int mean = (int) stats.getMean();
		int absoluteVariance = (int) Math.floor(variance / mean);
		StatisticalData statisticalData = new StatisticalData(false, mean, absoluteVariance);
		return statisticalData;
	}

	private IntervalXYDataset createXyDataset(String className) {
		final XYSeries series = new XYSeries(className);
		List<ClassInstanceCount> histoStats = memlogContext.getStats(className);
		Date firstDate = resolveFirstDate(histoStats);
		long firstSec = firstDate.getTime() / 1000;

		for (ClassInstanceCount dateAndNumber : histoStats) {
			long timeSec = dateAndNumber.getSamplingDate().getTime() / 1000;
			series.add(timeSec - firstSec, dateAndNumber.getNumberOfInstances());
		}
		final XYSeriesCollection dataset = new XYSeriesCollection(series);
		return dataset;
	}

	private Date resolveFirstDate(List<ClassInstanceCount> histoStats) {
		Date firstDate = null;
		for (ClassInstanceCount dateAndNumber : histoStats) {
			long time = dateAndNumber.getSamplingDate().getTime();
			if (firstDate == null || firstDate.getTime() > time) {
				firstDate = dateAndNumber.getSamplingDate();
			}
		}
		return firstDate;
	}
}
